﻿namespace ClassyBlog.Infrastructure.Specs.Unit
{
    using System;
    using System.Collections.Generic;
    using System.Dynamic;
    using System.Linq;
    using System.Web.Mvc;
    using System.Web.Routing;

    using Machine.Specifications;

    public class RouteMatchingSpec
    {
        private const string AppPathModifier = "/$(SESSION)";

        public RouteMatchingSpec(RouteCollection routes)
        {
            Routes = routes;
        }

        protected RouteCollection Routes { get; private set; }

        public RouteMatchingResult Get(string url)
        {
            var routeData = GetRouteData(url, HttpVerbs.Get);

            return CreateResult(routeData);
        }

        public RouteMatchingResult Post(string url)
        {
            var routeData = GetRouteData(url, HttpVerbs.Post);

            return CreateResult(routeData);
        }

        public RouteMatchingResult Put(string url)
        {
            var routeData = GetRouteData(url, HttpVerbs.Put);

            return CreateResult(routeData);
        }

        public RouteMatchingResult Delete(string url)
        {
            var routeData = GetRouteData(url, HttpVerbs.Delete);

            return CreateResult(routeData);
        }

        private static RouteMatchingResult CreateResult(RouteData routeData)
        {
            return (routeData == null) ?
                   null :
                   new RouteMatchingResult(routeData);
        }

        private RouteData GetRouteData(string url, HttpVerbs httpVerb)
        {
            var httpContext = MvcTestHelper.CreateHttpContext(url, httpVerb);
            var routeDate = Routes.GetRouteData(httpContext);

            return routeDate;
        }
    }

    public class RouteMatchingResult
    {
        private IDictionary<string, object> dynamicProperties;

        public RouteMatchingResult(RouteData routeData)
        {
            Controller = routeData.GetRequiredString("controller");
            Action = routeData.GetRequiredString("action");
            Values = new ValuesDictionary(routeData.Values.ToDictionary(d => d.Key, d => d.Value, StringComparer.OrdinalIgnoreCase));
        }

        public string Controller { get; private set; }

        public string Action { get; private set; }

        public dynamic Values { get; private set; }

        private sealed class ValuesDictionary : DynamicObject
        {
            private readonly IDictionary<string, object> dictinary;

            public ValuesDictionary(IDictionary<string, object> dictinary)
            {
                this.dictinary = dictinary;
            }

            public override bool TryGetMember(GetMemberBinder binder, out object result)
            {
                return dictinary.TryGetValue(binder.Name, out result);
            }

            public override bool TrySetMember(SetMemberBinder binder, object value)
            {
                dictinary[binder.Name] = value;
                return true;
            }
        }
    }

    public abstract class RoutingSpec
    {
        protected static RouteMatchingSpec app;
        protected static RouteMatchingResult result;

        Establish context = () =>
        {
            var routes = new RouteCollection();
            new RouteRegistrations(routes).Register();

            app = new RouteMatchingSpec(routes);
        };
    }

    [Subject(typeof(RouteRegistrations))]
    public class when_getting_application_root : RoutingSpec
    {
        Because of = () => result = app.Get("~/");

        It should_match_home_index = () =>
        {
            result.Controller.ShouldEqual("home");
            result.Action.ShouldEqual("index");
        };
    }

    [Behaviors]
    public class ArchivedPostList
    {
        protected static RouteMatchingSpec app;
        protected static RouteMatchingResult result;

        It should_match_posts_index = () =>
        {
            result.Controller.ShouldEqual("posts");
            result.Action.ShouldEqual("index");
        };

        It should_have_correct_page_number = () =>
            ((string)result.Values.Page).ShouldEqual("5");
    }

    [Behaviors]
    public class YearlyArchivedPostList
    {
        protected static RouteMatchingSpec app;
        protected static RouteMatchingResult result;

        It should_have_correct_year = () =>
            ((string)result.Values.Year).ShouldEqual("2011");
    }

    [Behaviors]
    public class MonthlyArchivedPostList
    {
        protected static RouteMatchingSpec app;
        protected static RouteMatchingResult result;

        It should_have_correct_month = () =>
            ((string)result.Values.Month).ShouldEqual("04");
    }

    [Subject(typeof(RouteRegistrations))]
    public class when_creating_route_registration_with_null_route_table
    {
        static Exception exception;

        Because of = () => exception =
            Catch.Exception(() => new RouteRegistrations(null));

        It should_throw_exception = () =>
            exception.ShouldBeOfType<ArgumentNullException>();
    }

    [Subject(typeof(RouteRegistrations))]
    public class when_getting_post_list : RoutingSpec
    {
        Because of = () => result = app.Get("~/posts");

        It should_not_match_posts_index = () =>
            result.Controller.ShouldNotEqual("posts");
    }

    [Subject(typeof(RouteRegistrations))]
    public class when_getting_paged_post_list : RoutingSpec
    {
        Because of = () => result = app.Get("~/posts/5");

        Behaves_like<ArchivedPostList> archivedList;
    }

    [Subject(typeof(RouteRegistrations))]
    public class when_getting_daily_post_list : RoutingSpec
    {
        Because of = () => result = app.Get("~/posts/5/2011/04/02");

        It should_have_correct_day = () =>
            ((string)result.Values.Day).ShouldEqual("02");

        Behaves_like<ArchivedPostList> archivedList;

        Behaves_like<YearlyArchivedPostList> yearlyArchivedList;

        Behaves_like<MonthlyArchivedPostList> monthlyArchivedList;
    }

    [Subject(typeof(RouteRegistrations))]
    public class when_getting_monthly_post_list : RoutingSpec
    {
        Because of = () => result = app.Get("~/posts/5/2011/04");

        Behaves_like<ArchivedPostList> archivedList;

        Behaves_like<YearlyArchivedPostList> yearlyArchivedList;

        Behaves_like<MonthlyArchivedPostList> monthlyArchivedList;
    }

    [Subject(typeof(RouteRegistrations))]
    public class when_getting_yearly_post_list : RoutingSpec
    {
        Because of = () => result = app.Get("~/posts/5/2011");

        Behaves_like<ArchivedPostList> archivedList;

        Behaves_like<YearlyArchivedPostList> yearlyArchivedList;
    }

    [Subject(typeof(RouteRegistrations))]
    public class when_getting_post : RoutingSpec
    {
        Because of = () => result = app.Get("~/posts/hello-world");

        It should_match_posts_show = () =>
        {
            result.Controller.ShouldEqual("posts");
            result.Action.ShouldEqual("show");
        };

        It should_have_correct_slug = () =>
            ((string)result.Values.Slug).ShouldEqual("hello-world");
    }

    [Subject(typeof(RouteRegistrations))]
    public class when_getting_categorized_page_list : RoutingSpec
    {
        Because of = () => result = app.Get("~/2/programming/dotnet");

        It should_match_pages_index = () =>
        {
            result.Controller.ShouldEqual("pages");
            result.Action.ShouldEqual("index");
        };

        It should_have_correct_page_number = () =>
            ((string)result.Values.Page).ShouldEqual("2");

        It should_have_correct_relative_path = () =>
            ((string)result.Values.RelativePath)
            .ShouldEqual("programming/dotnet");
    }

    [Subject(typeof(RouteRegistrations))]
    public class when_getting_paged_page_list : RoutingSpec
    {
        Because of = () => result = app.Get("~/3");

        It should_match_pages_index = () =>
        {
            result.Controller.ShouldEqual("pages");
            result.Action.ShouldEqual("index");
        };

        It should_have_correct_page_number = () =>
            ((string)result.Values.Page).ShouldEqual("3");

        It should_not_have_relative_path = () =>
            ((UrlParameter)result.Values.RelativePath)
            .ShouldBeTheSameAs(UrlParameter.Optional);
    }

    [Subject(typeof(RouteRegistrations))]
    public class when_gettting_page : RoutingSpec
    {
        Because of = () =>
            result = app.Get("~/programming/dotnet/static-language");

        It should_match_pages_show = () =>
        {
            result.Controller.ShouldEqual("pages");
            result.Action.ShouldEqual("show");
        };

        It should_have_correct_relative_path = () =>
            ((string)result.Values.RelativePath)
            .ShouldEqual("programming/dotnet/static-language");
    }

    [Subject(typeof(RouteRegistrations))]
    public class when_getting_external_content : RoutingSpec
    {
        Because of = () => result = app.Get("~/proxy");

        It should_match_content_proxy_get = () =>
        {
            result.Controller.ShouldEqual("contentproxy");
            result.Action.ShouldEqual("get");
        };
    }
}